#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Network Hoover - automated testing tool

Network hoover is meant for information security testing purposes. It is connected
between two machines which exchange information by using networking protocols. The
hoover listens the traffic coming from the other side, captures it, analyses its
content, fuzzes the payload if possible and sends the packet to the target side.
The traffic coming from the target's side will be just forwarded, if so defined.


Author: Henri Timonen
Version: 0.3
'''

import pcap
import sys, os
import termios, fcntl
import copy
import threading, thread
import socket
import argparse
import string
import struct
import time
import random
import logging
import array
import exceptions, errno

from datetime import datetime
from collections import deque

from trafficParser import trafficParser
from packetParser import packetParser

#Fuzzers are classes and are found from a file defined in fuzzerModule
import pyclbr
from fuzzers import *
fuzzerModule = "fuzzers" #Means module named 'fuzzers.py'

protocols = {socket.IPPROTO_UDP:'udp',
             socket.IPPROTO_TCP:'tcp'}

proxyQueue = deque()
proxyLock = thread.allocate_lock()
proxyList = []
proxyList2 = []

duplOpt = [False, False]
duplArray = []
fuzzing = False
packetnro = 0
packetnro2 = 0
pInfo = False
spoofingOpts = [False]

Tlock1 = thread.allocate_lock()
Tlock2 = thread.allocate_lock()

loggingQueue = deque()
loggingQueue2 = deque()
packetQueue = deque()

decoder = packetParser()

TERMIOS = termios

'''
Termios provides an interface to the POSIX calls for tty I/O control.
Here it is used to listen key presses without disturbing the program flow.
Function listens key presses and returns the char that is currently pressed
or just passes if no keys pressed.
'''
def getkey():
    
    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    new = termios.tcgetattr(fd)
    new[3] = new[3] & ~TERMIOS.ICANON & ~TERMIOS.ECHO
    termios.tcsetattr(fd, TERMIOS.TCSANOW, new)
    oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
    c = None
    try:
        c = sys.stdin.read(1)
    except IOError:
        pass
    finally:
        termios.tcsetattr(fd, TERMIOS.TCSAFLUSH, old)
        fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
    return c

'''
This class will take care of forwarding incoming packages from output
interface to input interface, but only if proxy mode is allowed to be.
Packages are forwarded using OSI layer 2.
'''
class proxyThread(threading.Thread):

    def __init__(self, interface):
        threading.Thread.__init__(self)
        self.interface = interface

        #Socket for forwarding stuff
        self.sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
        self.sock.bind((interface[1],0))

        self.sLimit = 65535

        self.operate = True


    def run(self):

        while self.operate:

            while len(proxyQueue)!=0:

                packet = proxyQueue.popleft()

                global packetnro2
                packetnro2+=1

                global proxyList2
                proxyList2.append(packet)

                try:
                    self.sock.send(packet)
            
                except socket.error as e:
                    pLen = len(packet)
                    if cmp(e[0],errno.EMSGSIZE)==0 and pLen<self.sLimit:
                        if not self.modifyMTU(pLen, self.sock, self.interface[1]):
                            self.sLimit = pLen
                except:
                    print "Unhandled exception: {}".format(sys.exc_info()[0])

                packet = ""

            #Sleep for a while if queue is empty
            time.sleep(0.0005)

        #Thread execution is terminated
        Tlock2.release()
                        

    '''
    In case of too large packet for interface to send, try to modify
    interface's MTU value by calling directly ioctl function which is
    responsible for it in unix environment.
    '''
    def modifyMTU(self, pktlen, sck, iface):
        
        print "Packet too large, trying to modify interface's MTU value."
        try:
            fcntl.ioctl(sck, 0x8922,
                        struct.pack('<16sH',iface,pktlen)+'\x00'*14)
            print "MTU value for interface '{}' changed to {}".format(iface,pktlen)
            return True
        except Exception as ex:
            print "MTU value could not be changed, ignoring packets larger than {} bytes".format(pktlen-1)
            return False
        

    def terminate(self):

        Tlock2.acquire()
        self.operate = False


'''
This class reads incoming packages from a queue and process them by
going through rules and determining if package needs to be fuzzed
or just forwarded.
'''
class forwarderThread(threading.Thread):

    def __init__(self, interface, fuzzerlist, log, filtering, interval=None):
        threading.Thread.__init__(self)
        self.interface = interface
        self.fuzzerList = copy.deepcopy(fuzzerlist)
        self.log = log
        self.filtering = filtering

        self.p = {}
        self.sLimit = 65535
        self.operate = True
        self.sendAgainCtr = 2

        self.sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
        self.sock.bind((interface,0))

        #Launcher class interacts with the fuzzers
        if rules.fuzzing_options!=None:
            self.launcher = Launcher(fuzzerlist, rules.fuzzing_options)
        else:
            self.launcher = Launcher(fuzzerlist, [])

        if interval:
            self.timerInterval = interval[0]
        else:
            self.timerInterval = None
        self.activeTimer = False

        #Create fake addresses in advance to speed up the execution
        if spoofingOpts[0]:

            self.SAddr4 = ""
            self.DAddr4 = ""
            self.SAddr6 = ""
            self.DAddr6 = ""
            self.ESrc = ""
            self.EDst = ""
            
            if 'ipsrc' in spoofingOpts:
                self.SAddr4 = socket.inet_pton(socket.AF_INET, spoofingOpts[spoofingOpts.index('ipsrc')+1])
            if 'ipdst' in spoofingOpts:
                self.DAddr4 = socket.inet_pton(socket.AF_INET, spoofingOpts[spoofingOpts.index('ipdst')+1])
            if 'ip6src' in spoofingOpts:
                self.SAddr6 = socket.inet_pton(socket.AF_INET6, spoofingOpts[spoofingOpts.index('ip6src')+1])
            if 'ip6dst' in spoofingOpts:
                self.DAddr6 = socket.inet_pton(socket.AF_INET6, spoofingOpts[spoofingOpts.index('ip6dst')+1])
            if 'ethsrc' in spoofingOpts:
                self.ESrc = spoofingOpts[spoofingOpts.index('ethsrc')+1]
            if 'ethdst' in spoofingOpts:
                self.EDst = spoofingOpts[spoofingOpts.index('ethdst')+1]        
        

    def run(self):

        while self.operate:

            while len(packetQueue)!=0:
                
                self.p['raw'] = packetQueue.popleft()

                if rules.log_unaltered:
                    loggingQueue2.append([repr(time.time()),self.p['raw']])

                global packetnro
                packetnro+=1

                #If fixed addresses are defined, try to change it 
                if spoofingOpts[0]:
                    #If IPv4 packet, change the address and calculate checksum
                    if ((self.SAddr4 or self.DAddr4) and len(self.p['raw'])>33 and 
                        socket.ntohs(struct.unpack('H',self.p['raw'][12:14])[0])==2048):
                        originalRaw = self.p['raw']
                        originalDA = ""
                        originalSA = ""
                        try:
                            header = "{}{}{}".format(self.p['raw'][14:24],"\x00\x00",self.p['raw'][26:34])
                            if self.SAddr4 and self.DAddr4:
                                originalSA = header[12:16]
                                originalDA = header[16:20]
                                header = header[0:12] + self.SAddr4 + self.DAddr4
                                header = header[0:10] + self.ip_checksum(header) + header[12:20]
                            elif self.SAddr4:
                                originalSA = header[12:16]
                                header = header[0:12] + self.SAddr4 + header[16:20]
                                header = header[0:10] + self.ip_checksum(header) + header[12:20]
                            elif self.DAddr4:
                                oritinalDA = header[16:20]
                                header = header[0:16] + self.DAddr4
                                header = header[0:10] + self.ip_checksum(header) + header[12:20]
                            self.p['raw'] = self.p['raw'][0:14] + header + self.p['raw'][34:]
                        except:
                            print "Error with IPv4 spoofing."
                            self.p['raw'] = originalRaw

                    #If IPv6 packet, change the address(es)
                    if ((self.SAddr6 or self.DAddr6) and len(self.p['raw'])>53 and
                        socket.ntohs(struct.unpack('H',self.p['raw'][12:14])[0])==34525):
                        originalRaw = self.p['raw']
                        originalSA = ""
                        originalDA = ""
                        try:
                            header = self.p['raw'][14:54]
                            if self.SAddr6 and self.DAddr6:
                                originalSA = header[8:24]
                                originalDA = header[24:40]
                                header = header[0:8] + self.SAddr6 + self.DAddr6
                            elif self.SAddr6:
                                originalSA = header[8:24]
                                header = header[0:8] + self.SAddr6 + header[24:40]
                            elif self.DAddr6:
                                originalDA = header[24:40]
                                header = header[0:24] + self.DAddr6
                            self.p['raw'] = self.p['raw'][0:14] + header + self.p['raw'][54:]
                        except:
                            print "Error with IPv6 spoofing."
                            self.p['raw'] = originalRaw
                            
                    #Change Ethernet addresses if so defined in spoofing options
                    if ("ethsrc" in spoofingOpts or "ethdst" in spoofingOpts) and len(self.p['raw'])>13:
                        originalRaw = self.p['raw']
                        try:
                            header = self.p['raw'][0:14]
                            if ('ethsrc' in spoofingOpts) and ('ethdst' in spoofingOpts):
                                header = self.EDst + self.ESrc + header[12:14]
                            elif 'ethsrc' in spoofingOpts:
                                header = header[0:6] + self.ESrc + header[12:14]
                            elif 'ethdst' in spoofingOpts:
                                header = self.EDst + header[6:14]
                            self.p['raw'] = header + self.p['raw'][14:]
                        except:
                            print "Error with Ethernet spoofing."
                            self.p['raw'] = originalRaw
                    
                try:
                    '''If duplication enabled, randomly decide if packet should
                    be duplicated. If so, set a flag and wait until packet is
                    duplicated before trying randomly again.'''
                    if duplOpt[0] and not duplOpt[1]:
                        if random.random() <= rules.duplicate_probability[0]:
                            duplOpt[1] = True
                
                    #No sense in going further if fuzzing is not allowed
                    if not fuzzing:
                        if pInfo:
                            print "\n#{0}  Length: {1}".format(packetnro,len(self.p['raw']))
                        self.forwarderFunction(self.p['raw'])
                        raise NoError()
            
                    temp = decoder.packetParse(self.p['raw'])
                    #Parser should return negative value if parsing were to be erronous
                    if temp<0:
                        self.forwarderFunction(self.p['raw'])
                        raise NoError()
                    self.p.update(temp)

                    #Drop the packet if it contains certain protocols
                    if rules.drop:
                        for item in rules.drop:
                            if self.p[item]:
                                raise NoError()

                    if pInfo:
                        try:
                            if rules.level[0]>=0:
                                print "\n#{0}  Length: {1}".format(packetnro,len(self.p['raw']))

                            if rules.level[0]>=1:
                                #TODO: Add ethernet addresses if needed
                                pass

                            if rules.level[0]>=2:
                                if self.p['ip']:
                                    print "  IP adressess: source {0}, destination {1}".format(
                                        self.p['ip_source_address'], self.p['ip_destination_address'])

                                    if rules.level[0]>=3:
                                        if cmp(protocols[self.p['ip_protocol']],"udp")==0:
                                            print "  UDP packet, ports: source {0}, destination {1}".format(
                                                self.p['udp_source_port'],self.p['udp_destination_port'])
                                        elif cmp(protocols[self.p['ip_protocol']],"tcp")==0:
                                            print "  TCP packet, ports: source {0}, destination {1}".format(
                                                self.p['tcp_source_port'],self.p['tcp_destination_port'])
                                    
                                elif self.p['arp']:
                                    print "  ARP message"

                        except KeyError as e:
                            print e

                    if len(self.p['payload'])==0:
                        self.forwarderFunction(self.p['raw'])
                        raise NoError()

                    #This goes through the filtering rules, if any
                    if self.filtering:

                        fValue = False

                        if 'ipsrc' in self.filtering:
                            if 'ipsrc' in spoofingOpts and originalSA:
                                if cmp(socket.inet_ntoa(originalSA),self.filtering['ipsrc'])==0:
                                    fValue = True
                                else:
                                    raise ValueError
                            else:
                                if cmp(self.p['ip_source_address'],self.filtering['ipsrc'])==0:
                                    fValue = True
                                else:
                                    raise ValueError

                        if 'ipdst' in self.filtering:
                            if 'ipdst' in spoofingOpts and originalDA:
                                if cmp(socket.inet_ntoa(originalDA),self.filtering['ipdst'])==0:
                                    fValue = True
                                else:
                                    raise ValueError
                            else:
                                if cmp(self.p['ip_destination_address'],self.filtering['ipdst'])==0:
                                    fValue = True
                                else:
                                    raise ValueError

                        if 'proto' in self.filtering:
                            if (protocols[self.p['ip_protocol']] in self.filtering['proto']):
                                fValue = True
                            else:
                                raise ValueError

                        if 'srcport' in self.filtering:
                            if (('udp_source_port' in self.p) and
                                (cmp(self.p['udp_source_port'],self.filtering['srcport'])==0)):
                                fValue = True
                            elif (('tcp_source_port' in self.p) and
                                  (cmp(self.p['tcp_source_port'],self.filtering['srcport'])==0)):
                                fValue = True
                            else:
                                raise ValueError

                        if 'dstport' in self.filtering:
                            if (('udp_destination_port' in self.p) and
                                (cmp(self.p['udp_destination_port'],self.filtering['dstport'])==0)):
                                fValue = True
                            elif (('tcp_destination_port' in self.p) and
                                  (cmp(self.p['tcp_destination_port'],self.filtering['dstport'])==0)):
                                fValue = True
                            else:
                                raise ValueError

                        if not fValue:
                            self.forwarderFunction(self.p['raw'])
                            raise NoError()

                    #If 'None' is not currently selected the package will be accepted,
                    #payload will be fuzzed and package is rebuild.
                    if self.fuzzerList[0]:
                        #If fuzzing probability is defined randomly decide whether
                        #the payload should be fuzzed or not.
                        if rules.fuzzing_probability:
                            if random.random() <= rules.fuzzing_probability[0]:
                                self.fuzzerFunction()
                            else:
                                self.forwarderFunction(self.p['raw'])
                        else:
                            self.fuzzerFunction()
                    else:
                        self.forwarderFunction(self.p['raw'])

                except KeyError:
                    self.forwarderFunction(self.p['raw'])
                except ValueError:
                    self.forwarderFunction(self.p['raw'])
                except NoError:
                    pass
                  
                self.p.clear()

            #Sleep a while if queue is empty
            time.sleep(0.0005)

        #Thread execution terminates.
        Tlock1.release()
            

    '''
    Function takes the given network package, fuzzes the separated payload,
    build a new package for the new (and broken) payload and sends it forward.
    Libnet's write and get raw package functions are not utilized because they
    cause unknown problems with hoover's proxy feature. 
    '''
    def fuzzerFunction(self):

        try:
            #Fuzz the payload by calling the fuzzing class
            data = self.launcher.dataFuzz(self.p['payload'],self.fuzzerList[0])

            #Packet building from the highest layer to the lowest

            packetToSend = ""
            ip_opt = False
            header = ""
            data_len = len(data)
            raw_len = len(self.p['raw'])
            pointer = raw_len - len(self.p['payload'])
            
            #Take the raw packet
            temp = self.p['raw']
            #Remove old payload and add new one
            temp = temp[:pointer] + data

            '''Application layer protocol building not supported yet.'''

            #Build transport layer header (UDP, TCP)
            if self.p['udp'] and cmp(protocols[self.p['ip_protocol']],"udp")==0:
                pointer -= 8
                header = self.build_udp_header(segment=temp[pointer:],
                                               ipsrc=self.p['ip_source_address'],
                                               ipdst=self.p['ip_destination_address'],
                                               ipv=self.p['ip_version'])
                temp = temp[:pointer] + header + temp[(pointer+8):]

            elif self.p['tcp'] and cmp(protocols[self.p['ip_protocol']],"tcp")==0:
                amount = 0
                if self.p['tcp_data_offset'] > 5:
                    amount = self.p['tcp_data_offset']*4
                else:
                    amount = 20

                pointer -= amount

                header = self.build_tcp_header(segment=temp[pointer:],
                                               ipsrc=self.p['ip_source_address'],
                                               ipdst=self.p['ip_destination_address'],
                                               ipv=self.p['ip_version'])
                temp = temp[:pointer] + header + temp[(pointer+amount):]

            #Build network layer header (IPv4, IPv6)
            if self.p['ip']:

                if 'ip_options' in self.p:
                    ip_opt = True
            
                if self.p['ip_version'] == 4:
                    if not ip_opt:
                        pointer -= 20
                        header = self.build_ipv4_header(dgram=temp[pointer:])
                        temp = temp[:pointer] + header + temp[(pointer+20):]
                    else:
                        pointer -= self.p['ip_header_len']*4
                        header = self.build_ipv4_header(dgram=temp[pointer:])
                        temp = temp[:pointer] + header + temp[(self.p['ip_header_len']*4):]

                elif self.p['ip_version'] == 6:
                    pointer -= 40
                    header = self.build_ipv6_header(dgram=temp[pointer:])
                    temp = temp[:pointer] + header + temp[(pointer+40):]

            #Build data-link layer header (Ethernet)
            if self.p['eth']:

                pVal = 0

                if pointer > 14:
                    if self.p['llc_header']:
                        pointer -= 22
                        pVal = 22
                    else:
                        pointer -=14
                        pVal = 14
                else:
                    pointer = 0
                    pVal = 14
                
                #if pointer > 14:
                #    pointer -= 14
                #else:
                #    pointer = 0

                header = self.build_ethernet_header(frame=temp[pointer:], llc=self.p['llc_header'])
                temp = header + temp[(pointer+pVal):]

            packetToSend = temp

            if rules.proxy:
                global proxyList
                proxyList.append(packetToSend)

            try:
                self.sock.send(packetToSend)

                if self.log['log']:
                    if self.log['format']=='pcap':
                        loggingQueue.append([repr(time.time()),packetToSend])
                    elif self.log['format']=='default':
                        port = [0,0]
                        message=["len {},".format(len(packetToSend))]

                        if self.p['udp']:
                            port[0] = self.p['udp_source_port']
                            port[1] = self.p['udp_destination_port']
                            message.append("UDP-packet,")
                        elif self.p['tcp']:
                            port[0] = self.p['tcp_source_port']
                            port[1] = self.p['tcp_destination_port']
                            message.append("TCP-packet,")

                        if self.p['ip'] and self.p['ip_version']==4:
                            message.append("IPv4,")
                        elif self.p['ip'] and self.p['ip_version']==6:
                            message.append("IPv6,")
                            
                        message.append("{}:{} > {}:{}, fuzzer: {}".format(self.p['ip_source_address'],
                                                                   port[0],
                                                                   self.p['ip_destination_address'],
                                                                   port[1],
                                                                   self.getFuzzer()))
                        loggingQueue.append([
                            datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f"),
                            ' '.join(message),
                            packetToSend])

                global duplOpt
                if duplOpt[1]:
                    if rules.proxy: proxyList.append(packetToSend)
                    duplOpt[1] = False
                    self.sock.send(packetToSend)
                    if self.log['log']:
                        if self.log['format']=='pcap':
                            loggingQueue.append([repr(time.time()),packetToSend])
                        elif self.log['format']=='default':
                            loggingQueue.append([
                                datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f"),
                                "Duplicated packet.",
                                packetToSend])

            except socket.error as e:
                dataLen = len(packetToSend)
                if cmp(e[0],errno.EMSGSIZE)==0 and dataLen<self.sLimit:
                    if not self.modifyMTU(dataLen,self.sock,self.interface):
                        self.sLimit = dataLen

            self.sendAgainCtr = 2

        except KeyError as werr:
            print "KeyError while building package: {}".format(werr)
        except BuildError as xerr:
            print xerr
            self.forwarderFunction(self.p['raw'])
        except Exception as eks:
            '''
            If something unexpected happens while building/sending
            a packet, try to send it again at least once.
            '''
            print "Unhandled exception: {}".format(eks)

            if self.sendAgainCtr>0:
            
                packetQueue.appendleft(self.p['raw'])

                self.sendAgainCtr-=1

            else:
                self.sendAgainCtr = 2
            
    '''
    Functions for building protocol headers have each value added separately, because
    intentions were originally to also allow building headers from scratch, meaning
    without using values from received packets. This was not yet implemented due to
    time constraints.
    '''
    '''
    Builds and returns an UDP-header
    Parameters:
    - segment: Includes the old udp header and the payload
    - ipsrc, ipdst: IP addresses for calculating the checksum
    '''
    def build_udp_header(self, segment=None, ipsrc=None, ipdst=None, ipv=None):

        header = ""

        if not segment:
            raise BuildError("UDP", "No segment given.")

        if ipv not in [4,6]:
            raise BuildError("UDP", "Incorrect IP version for checksum calculation.")

        #Source and destination port number
        header += segment[0:4]
        #Length of udp header and data
        header += struct.pack('!H', len(segment))
        #Checksum. (Not optional for IPv6)
        if (segment[6:8]=="\x00\x00") and (ipv==4):
            header += "\x00\x00"
        else:
            #Checksum has to be calculated
            source = ""
            destination = ""

            if (not ipsrc) or (not ipdst):
                raise BuildError("UDP", "IP source or destination not given, required for checksum calculation.")

            #Check if the addresses are in binary format
            try:
                if ipv == 4:
                    source = socket.inet_pton(socket.AF_INET, ipsrc)
                    destination = socket.inet_pton(socket.AF_INET, ipdst)
                elif ipv == 6:
                    source = socket.inet_pton(socket.AF_INET6, ipsrc)
                    destination = socket.inet_pton(socket.AF_INET6, ipdst)
            except:
                source = ipsrc
                destination = ipdst

            segment = segment[:6] + "\x00\x00" + segment[8:]
    
            header += self.tcp_checksum(segment, source, destination, "\x11")

        return header
        
    '''
    Builds and returns an TCP-header
    Parameters:
        - segment (If build_new is false): TCP segment as a raw data
        - ipsrc: IP source address for checksum calculation
        - ipdst: IP destination address for checksum calculation
    '''
    def build_tcp_header(self, segment=None, ipsrc=None, ipdst=None, ipv=None):

        header = ""
        source = ""
        destination = ""

        if not segment:
            raise BuildError("TCP", "No segment given.")

        if (not ipsrc) or (not ipdst):
            raise BuildError("TCP", "IP source or destination not given, required for checksum calculation.")

        if ipv not in [4,6]:
            raise BuildError("TCP", "Incorrect IP version for checksum calculation.")

        #Check if the addresses are in binary format
        try:
            if ipv == 4:
                source = socket.inet_pton(socket.AF_INET, ipsrc)
                destination = socket.inet_pton(socket.AF_INET, ipdst)
            elif ipv == 6:
                source = socket.inet_pton(socket.AF_INET6, ipsrc)
                destination = socket.inet_pton(socket.AF_INET6, ipdst)
        except:
            source = ipsrc
            destination = ipdst

        #Set TCP checksum field to zero
        segment = segment[:16] + "\x00\x00" + segment[18:]

        #Source port
        header += segment[0:2]
        #Destination port
        header += segment[2:4]
        #Sequence number
        header += segment[4:8]
        #Acknowledgment number
        header += segment[8:12]
        #Data offset, Reserved bits and flags
        header += segment[12:14]
        #Window size
        header += segment[14:16]
        #Checksum
        header += self.tcp_checksum(segment, source, destination, "\x06")
        #Urgent pointer
        header += segment[18:20]

        #TCP-options, if there are any, and payload
        offset = (ord(segment[12]) & 0xf0) >> 4
        if offset > 4:
            header += segment[20:(offset*4)]
        
        return header

    '''
    Builds and returns an IPv4-header
    - dgram: The whole IPv4 datagram and data in a raw format
    '''
    def build_ipv4_header(self, dgram=None):

        header = ""

        if not dgram:
            raise BuildError("IPv4", "Datagram not provided")
        
        #Version number and internet header length
        header += dgram[0]
        #Differentiated services code point and explicit congestion notification
        header += dgram[1]
        #Total length of the header + data
        header += struct.pack('!H', len(dgram))
        #Identification, flags and fragment offset
        header += dgram[4:6]
        #Flags and fragment offset
        header += dgram[6:8]
        #Time to live
        header += dgram[8]
        #Protocol used in the data portion
        header += dgram[9]
        #Header checksum which will be calculated later
        header += "\x00\x00"
        #Source address and destination address
        header += dgram[12:20]
        #Possible options
        head_len = ord(dgram[0]) & 0x0f
        if head_len > 5:
            header += dgram[20:(head_len*4)]

        #Checksum calculation
        header = header[:10] + self.ip_checksum(header) + header[12:]

        return header

    '''Builds and returns an IPv6-header'''
    def build_ipv6_header(self, dgram=None):

        header = ""

        if not dgram:
            raise BuildError("IPv6","")

        #Version number, traffic class and flow label
        header += dgram[0:4]
        #Payload length
        header += struct.pack('!H', (len(dgram)-40))
        #Next header
        header += dgram[6]
        #Hop limit
        header += dgram[7]
        #Source address
        header += dgram[8:24]
        #Destination address
        header += dgram[24:40]

        return header

    '''
    Builds and returns an ethernet-header
    - frame: the whole data-link layer frame
    '''
    def build_ethernet_header(self, frame=None, llc=False):

        header = ""
            
        if not frame:
            raise BuildError("Ethernet", "Frame not given, header can't be build.")
            
        #Ethernet destination and source mac addresses
        header += frame[0:12]
        #Ethernet type
        header += frame[12:14]
        if llc:
            #SAP values and control
            header += frame[14:17]
            #SNAP values (organization code and ethertype)
            header += frame[17:22]

        return header


    '''Changes the current fuzzer by rotating list's values'''
    def fuzzerChange(self):

        if not self.fuzzerList or len(self.fuzzerList)==1:
            return
        else:
            self.fuzzerList.append(self.fuzzerList[0])
            self.fuzzerList.pop(0)
            print "\nNow fuzzing with: {}".format(self.fuzzerList[0])


    '''Returns currently used fuzzer, if any (else returns None).'''
    def getFuzzer(self):
        
        try:
            fuzzer = self.fuzzerList[0]
        except IndexError:
            return None
        
        return fuzzer

    '''
    Function calculates TCP and UDP headers checksum for IPv4 and IPv6.
    '''
    def tcp_checksum(self, segment, ip_src, ip_dst, prot):

        buff = array.array('B')
        pseudo_header = ""
        
        '''
        Pseudo header consists of IP source and destination
        addresses, reserved bits, IP protocol field and TCP
        length (TCP header + data) fields.
        '''
        #Pseudo header for IPv4 protocol
        if len(ip_src) == 4:
            pseudo_header += ip_src
            pseudo_header += ip_dst
            pseudo_header += "\x00"
            pseudo_header += prot
            pseudo_header += struct.pack('!H', len(segment))
            
        #Pseudo header for IPv6 protocol
        elif len(ip_src) == 16:
            pseudo_header += ip_src
            pseudo_header += ip_dst
            pseudo_header += struct.pack('!I', len(segment))
            pseudo_header += "\x00\x00\x00"
            pseudo_header += prot

        buff.fromstring(pseudo_header)
        buff.fromstring(segment)

        nleft = len(buff)
        checksum = 0
        pos = 0

        while nleft > 1:
            checksum = buff[pos] * 256 + (buff[pos+1] + checksum)
            pos += 2
            nleft -= 2

        if nleft == 1:
            checksum += buff[pos] * 256

        #Checksum normalization
        checksum = (checksum >> 16) + (checksum & 0xFFFF)
        checksum += (checksum >> 16)
        checksum = (~checksum & 0xFFFF)

        return struct.pack('!H', checksum)


    '''
    Function calculates checksum for given ip header.
    First all the 16 bit words are summed, then the value is divided into 2 16 bit
    words and then they are summed. Finally it takes 1's complement and returns.
    '''
    def ip_checksum(self, msg):

        ip_header_sum = sum([struct.unpack("!H",msg[i:i+2])[0] for i in range(0,len(msg),2)])
        ip_header_sum = (ip_header_sum & 0xffff)+(ip_header_sum >> 16 & 0xffff)
        #Python doesn't support 1's complement so it has to be done like this
        ip_header_sum = ~ip_header_sum & 0xffff
        
        return struct.pack("!H", ip_header_sum)


    '''Just forwards the packet using layer 2 and checks possible errors'''
    def forwarderFunction(self, data):

        if rules.proxy:
            global proxyList
            proxyList.append(data)

        try:
            self.sock.send(data)

            if self.log['log']:
                if self.log['format']=='pcap':
                    loggingQueue.append([repr(time.time()),data])
                elif self.log['format']=='default':
                    message="len {}, forwarded packet".format(len(data))
                    loggingQueue.append([
                        datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f"),
                        message,
                        data])

            global duplOpt
            if duplOpt[1]:
                if rules.proxy: proxyList.append(data)
                duplOpt[1] = False
                self.sock.send(data)
                if self.log['log']:
                    if self.log['format']=='pcap':
                        loggingQueue.append([repr(time.time()),data])
                    elif self.log['format']=='default':
                        loggingQueue.append([
                            datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f"),
                            "Duplicated packet.",
                            data])
            
        except socket.error as e:
            dataLen = len(data)
            if cmp(e[0],errno.EMSGSIZE)==0 and dataLen<self.sLimit:
                if not self.modifyMTU(dataLen,self.sock,self.interface):
                    self.sLimit = dataLen
        except Exception:
            print "Unhandled exception: {}".format(sys.exc_info()[0])
        

    '''
    In case of too large packet for interface to send, try to modify
    interface's MTU value by calling directly ioctl function which is
    responsible for it in unix environment.
    '''
    def modifyMTU(self, pktlen, sck, iface):
        print "Packet too large, trying to modify interface's MTU value."
        try:
            fcntl.ioctl(sck, 0x8922,
                        struct.pack('<16sH',iface,pktlen)+'\x00'*14)
            print "MTU value for interface '{}' changed to {}".format(iface,pktlen)
            return True
        except Exception as ex:
            print "MTU value could not be changed, ignoring packets larger than {} bytes".format(pktlen-1)
            return False


    '''Starts or stops fuzzer changing timer.'''
    def toggleTimer(self):
        
        if not self.activeTimer and self.timerInterval:
            self.__timer = changeFuzzerTimer(self)
            self.__timer.setInterval(self.timerInterval)
            self.__timer.start()
            self.activeTimer = True

            return 1

        elif self.activeTimer and self.timerInterval:
            self.__timer.terminate()
            self.activeTimer = False
            
            return 0

        else:
            return -1


    def terminate(self):

        Tlock1.acquire()
        self.operate = False
        if self.activeTimer:
            self.toggleTimer()


'''
Thread to take care of logging necessary traffic information.
Supposedly speeds up execution since other threads can focus on
packet building and sending.
'''
class loggerThread(threading.Thread):

    def __init__(self, logfile, options, logQueue):
        threading.Thread.__init__(self)
        self.operate = True
        self.logfile = logfile
        self.orderNumber = 0
        self.options = options
        self.logQueue = logQueue

    def run(self):

        '''
        Options define the desired logging format.
        Formats supported this far: Pcap
        Default format is easy to read and is used only by this program.
        '''
        if cmp(self.options['format'],'pcap')==0:

            logger = open(self.logfile, 'w')

            #Adding global header to the file
            logger.write("\xa1\xb2\xc3\xd4\x00\x02\x00\x04"+
                         "\x00\x00\x00\x00\x00\x00\x00\x00"+
                         "\x00\x00\xFF\xFF\x00\x00\x00\x01")

            conv=lambda x:''.join([chr(int(x[i:i+2],16)) for i in range(0,len(x),2)])
            pad = lambda y: y.zfill(8)
            padRight = lambda w: w+''.join(["0" for i in range(len(w),6)])
            
            while self.operate:
                while len(self.logQueue)>0:

                    packet = self.logQueue.popleft()
                
                    #Pcap packet header + actual data
                    sec, micro = packet[0].split('.')
                    if len(micro)!=6: micro = padRight(micro)
                    sec, micro = hex(int(sec))[2:], hex(int(micro))[2:]
                    micro = pad(micro)
                    sec, micro = conv(sec), conv(micro)
                    length = pad(hex(len(packet[1]))[2:])
                    length = conv(length)
                    logger.write(sec+micro+length+length+packet[1])

                time.sleep(0.1)

            logger.close()

        else:
            logging.basicConfig(filename=self.logfile,
                                format='%(message)s',
                                level=logging.DEBUG)

            logging.info(datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")+
                        " : -- Session started --")

            while self.operate:
                while len(self.logQueue)>0:

                    packet = self.logQueue.popleft()
                    self.orderNumber += 1

                    if self.options['level']=='all':
                        logging.info(("#%d  %s : %s\n[%s]")%
                                    (self.orderNumber,
                                    packet[0],
                                    packet[1],
                                    ','.join(["%02X" % ord(x) for x in packet[2]])))                            

                    else:
                        logging.info(("#%d  %s : %s")%
                                    (self.orderNumber,
                                    packet[0],
                                    packet[1]))

                time.sleep(0.1)

            logging.info(datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")+
                         " : -- Session ended --")
                    

    def terminate(self):
        self.operate = False


'''
Timer thread is meant for changing used fuzzer automatically every x seconds.
Runs on a separate thread and utilises thread events (set and wait) so that
execution can be terminated even while thread is waiting. This is not possible
with normal sleep.
'''
class changeFuzzerTimer(threading.Thread):

    def __init__(self, caller):
        threading.Thread.__init__(self)
        self.__caller = caller
        self.__finished = threading.Event()
        self.interval = 0

    def run(self):

        while 1:
            if self.__finished.isSet():
                return
            
            self.__caller.fuzzerChange()

            self.__finished.wait(self.interval)

    def setInterval(self, interval):
        self.interval = interval
            
    def terminate(self):
        self.__finished.set()


'''
Function for checking available interfaces and validity of the interfaces
given in command line arguments.
'''
def checkInterfaces(interface):

    devices = []
    if(pcap.findalldevs()):
        for name, descr, addrs, flags in pcap.findalldevs():
            if addrs:
                devices.append(name)
                if rules.list:
                    print "\nInterface name:",name
                    for addr, netmask, broadaddr, dstaddr in addrs:
                        print "    Address:",addr

    else:
        print "No network devices found."
        print "Do you have enough rights to access network devices?"
        return -1
    
    if not rules.list:
        for i in interface:
            if i not in devices:
                print ("Defined interface '{}' not found.".format(i))
                return -1
        print "Interfaces found...\n"


'''
Handles packets coming to input interface by pushing them to a queue.
Actual parsing and further sending is done in a separate thread.
'''
def handleInputPacket(pktlen, data, timestamp):
    if not data:
        return
    else:
        if rules.proxy:
            global proxyList2
            try:
                index = proxyList2.index(data)
                proxyList2.pop(index)
                if index>0:
                    proxyList2.pop(index-1)

                return

            except ValueError:
                pass
        
        packetQueue.append(data)


'''
Handles packets coming to output interface by pushing them to a queue.
Only used while in 'proxy'-mode.
'''
def handleOutputPacket(pktlen, data, timestamp):
    if not data:
        return
    else:
        try:
            global proxyList
            index = proxyList.index(data)
            proxyList.pop(index)
            if index>0:
                proxyList.pop(index-1)

            return
        
        except ValueError:
            pass

        proxyQueue.append(data)


'''
Hoover's settings can be given from a file, which makes continuous
usage a lot easier.
Function returns list of arguments in the case success and -1 otherwise.
Only lines beginning with # character are taken into consideration
'''
def parseArguments(filename):

    argList = []
    temp = ""
    try:
        argFile = open(filename,'r')
    except IOError:
        return -1
    
    for line in argFile.readlines():
        if line[0]=="#":
            for x in line[1:]:
                if x==" " and temp!="":
                    argList.append(temp)
                    temp = ""
                elif x!=" ":
                    temp+=x
                    
    argFile.close()

    return argList


def main(arg):

    parser=argparse.ArgumentParser(description='Network hoover for analyzing and corrupting network traffic between two entities',
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('-i', '--interfaces',
                        action="store",
                        nargs=2,
                        help='Input and output interface names separated with a empty space.')
    parser.add_argument('--list',
                        action="store_true",
                        default=False,
                        help='Lists all of the available interfaces')
    parser.add_argument('-p','--proxy',
                        action="store_true",
                        default=False,
                        help='Determines whether the proxy mode is activated or not.')
    parser.add_argument('--filter',
                        action="store",
                        nargs='+',
                        help="Allows packet filtering. Only those packets which adhere to the filtering rules are fuzzed. "+
                        "Possible options: source IP address (ipsrc), destination IP address (ipdst), source port (srcport), destination port (dstport) and protocol (proto, only 'udp' and 'tcp' supported). "+
                        "Filtering values are separated with a space, e.g. '-filter ipsrc 1.2.3.4 dstport 123 proto udp'")
    parser.add_argument('--fuzzers',
                        action="store_true",
                        default=False,
                        help='Lists all available fuzzers and displays further information.')
    parser.add_argument('-f','--fuzz',
                        action="store",
                        nargs='+',
                        help='Fuzzers to be utilized. To get more info about the fuzzers, type --fuzzers')
    parser.add_argument('-l','--log',
                        action="store",
                        nargs='+',
                        help="Enables logging of information to a specified file. "+
                        "Mode takes 1-3 arguments: mandatory logfile name and optional save format. "+
                        "Supported formats are default (hoover's own format) and pcap. If no format defined, hoover's own format will be used. "+
                        "Default format can be made more verbose by giving 'verbose' as a third argument.")
    parser.add_argument('--log-unaltered',
                        action="store_true",
                        default=False,
                        help="Saves also unaltered received packets. Requires normal logging to be enabled and only logs in pcap-format.")
    parser.add_argument('-r','--replay',
                        action="store",
                        nargs='+',
                        help="Sends all the packets again from the given logfile to defined interface."+
                        "\nTakes two arguments: logfile name and interface to be used. Optionally the sending interval can be given in milliseconds as a third argument.")
    parser.add_argument('--info',
                        action='store_true',
                        default=False,
                        help="Print information about received packages in real time."+
                        "Could affect performance since printing takes some time.")
    parser.add_argument('-dp','--duplicate-probability',
                        action="store",
                        type=float,
                        default=[0.02],
                        nargs=1,
                        help="Define probability of duplicate packet appearance. Value should be in range [0.01, 1.00].")
    parser.add_argument('--level',
                        action="store",
                        default=[10],
                        type=int,
                        nargs=1,
                        help="Defines the level to which extend headers are parsed and what part is handled as payload. Levels: 1 link layer, 2 network layer, 3 transport layer, 4 application layer.")
    parser.add_argument('--fuzzing-options',
                        action="store",
                        nargs='+',
                        help="Gives the fuzzer(s) additional options.")
    parser.add_argument('-s','--settings',
                        action='store',
                        nargs=1,
                        help="Determines the file name where hoover's settings are stored.")
    parser.add_argument('--spoofing',
                        action='store',
                        nargs='+',
                        help="Tries to change desired address to a fake one. Allowed parameter values are: ipsrc (=IPv4 source), ipdst (=IPv4 destination), ip6src (=IPv6 source), ip6dst (=IPv6 destination), ethsrc (=ethernet source), ethdst (=ethernet destination).")
    parser.add_argument('-t','--timer',
                        action='store',
                        nargs=1,
                        default=None,
                        type=int,
                        help="Defines how long time one individual fuzzer will be used (in seconds) before changing it to the other one defined.")
    parser.add_argument('--drop',
                        action='store',
                        nargs='+',
                        help="Drops packets which are using protocols defined here. Currently supported: tcp, udp, arp, icmp.")
    parser.add_argument('-fp','--fuzzing-probability',
                        action='store',
                        nargs=1,
                        type=float,
                        help="Defines the probability which will be used to determine if packet should be fuzzed or not. However, packet should be recognized to be fuzzable before this could be done. Values should be between [0.01, 1.0].")
    #parser.add_argument('--fuzzing-ratio',
    #                    action='store',
    #                    type=int,
    #                    nargs=1,
    #                    help="Defines how many fuzzed packet will be generated for each incoming packet. Allowed values are integers [2,10].")


    #Trying to initialize network hoover's different variables
    try:
        if not arg:
            raise InitError("No command line arguments given!")

        global rules
        rules = parser.parse_args(arg)

        if rules.settings!=None:
            arg = parseArguments(rules.settings[0])
            if arg==-1:
                raise InitError('File could not be processed.')
            rules = parser.parse_args(arg)

        #Session replay if defined
        if rules.replay:
            if (len(rules.replay) != 2) and (len(rules.replay) != 3):
                raise InitError("Replay takes exactly 2 or 3 arguments.")
            if checkInterfaces([rules.replay[1]])==-1:
                raise InitError("Interface error!")
            if len(rules.replay) == 3:
                try:
                    timing = int(rules.replay[2])
                except:
                    raise InitError("Incorrect replay timing value.")
                if timing <= 0:
                    raise InitError("Replay timing must be positive value.")
            else:
                timing = None
            tr = trafficParser(rules.replay[0], rules.replay[1], interval=timing)
            if tr.replaySession()<0:
                raise InitError("Error while replicating session.")
            print "Executed successfully! Exiting.."
            return

        #Listing of fuzzers and their operating principles
        if rules.fuzzers:
            fuzzerListing()
            return

        #Saving spoofing settings into an array
        if rules.spoofing!=None:
            global spoofingOpts
            for val in rules.spoofing:
                if val in ["ethsrc","ethdst"]:
                    spoofingOpts.append(val)
                    try:
                        address = rules.spoofing[rules.spoofing.index(val)+1]
                        temp = address.split(':')
                        if len(temp)!=6:
                            raise ValueError
                        address=''.join([chr(int(x,16)) for x in temp])
                        spoofingOpts.append(address)
                        spoofingOpts[0] = True
                    except:
                        spoofingOpts.pop()
                        print "Erronous ethernet address: '{}'. ignoring the value.".format(val)

                if val in ["ipsrc","ipdst"]:
                    spoofingOpts.append(val)
                    try:
                        address = rules.spoofing[rules.spoofing.index(val)+1]
                        temp = address.split('.')
                        if len(temp)!=4:
                            raise ValueError
                        for value in temp:
                            if int(value)>255 or int(value)<0:
                                raise ValueError
                        spoofingOpts.append(address)
                        spoofingOpts[0] = True
                    except:
                        spoofingOpts.pop()
                        print "Erronous IPv4 address: '{}'. Ignoring the value.".format(val)

                if val in ["ip6src","ip6dst"]:
                    spoofingOpts.append(val)
                    try:
                        address = rules.spoofing[rules.spoofing.index(val)+1]
                        temp = address.split(':')
                        if len(temp)!=8:
                            raise ValueError
                        for value in temp:
                            if value and (int(value, 16)>65535 or int(value, 16)<0):
                                raise ValueError
                            else:
                                temp[temp.index(value)] = value.zfill(4)
                        spoofingOpts.append(":".join(temp))
                        spoofingOpts[0] = True
                    except:
                        spoofingOpts.pop()
                        print "Erronous IPv6 address: '{}'. Ignoring the value.".format(val)
                
        #Going through given interfaces and checking their validity
        if rules.interfaces==None:
            if rules.list:
                checkInterfaces([])
                print ""
                sys.exit(1)
            raise InitError("No interfaces defined!")

        interfaces = rules.interfaces

        if checkInterfaces(interfaces)==-1:
            raise InitError("Interface error!")

        '''
        Goes through the list of fuzzers the user has given and tries
        to locate them from a file where all of the fuzzers are defined.
        It's assumed that each fuzzer is separate class.
        '''
        fuzzerList = []
        global fuzzing
        if rules.fuzz!=None:

            for x in rules.fuzz:
                if x in pyclbr.readmodule(fuzzerModule, [os.getcwd()]):
                    fuzzerList.append(x)
                elif x=='Duplicate':
                    if len(rules.fuzz)==1: fuzzing = False
                    #Initialize duplicate sender
                    global duplOpt
                    duplOpt[0] = True
                    #Check whether the given duplicate probability is in correct range.
                    if rules.duplicate_probability[0]<0.01 or rules.duplicate_probability[0]>1.0:
                        raise InitError("Duplicate-opt value not in correct range [0.01, 1.00]")
            #Enable fuzzing if correct fuzzers defined in rules
            if fuzzerList:
                fuzzing = True
                fuzzerList.append(None)

        #If fuzzing is allowed and probability defined, initialize probability array
        if fuzzing and rules.fuzzing_probability:
            if rules.fuzzing_probability[0]<0.01 or rules.fuzzing_probability[0]>1.0:
                raise InitError("Fuzzing probability value not in correct range [0.01, 1.0].")
        elif not fuzzing and rules.fuzzing_probability:
            raise InitError("Fuzzing probability can't be defined if fuzzing is not allowed.")

        #Check for filtering settings and save them
        filtering = {}
        if rules.filter:
            if "ipsrc" in rules.filter:
                sa = rules.filter[rules.filter.index("ipsrc") + 1]
                try:
                    socket.inet_pton(socket.AF_INET, sa)
                except:
                    raise InitError("Illegal source address in filtering options.")
                filtering['ipsrc'] = sa
                
            if "ipdst" in rules.filter:
                da = rules.filter[rules.filter.index("ipdst") + 1]
                try:
                    socket.inet_pton(socket.AF_INET, da)
                except:
                    raise InitError("Illegal destination address in filtering options.")
                filtering['ipdst'] = da
                
            if "srcport" in rules.filter:
                sp = int(rules.filter[rules.filter.index("srcport") + 1])
                if (sp <= 0) or (sp > 65535):
                    raise InitError("Source port in filtering options out of range.")
                filtering['srcport'] = sp
                
            if "dstport" in rules.filter:
                dp = int(rules.filter[rules.filter.index("dstport") + 1])
                if (dp <= 0) or (dp > 65535):
                    raise InitError("Destination port in filtering options out of range.")
                filtering['dstport'] = dp
                
            if "proto" in rules.filter:
                protoArr = []
                for proto in rules.filter[(rules.filter.index("proto")+1):]:
                    if proto in ["ipsrc","ipdst","srcport","dstport"]:
                        break
                    elif proto not in ["udp","tcp"]:
                        raise InitError("Invalid protocol '{}' in filtering options.".format(proto))
                    protoArr.append(proto)
                filtering['proto'] = protoArr

        global pInfo
        if rules.info: pInfo = True

        #Proxy-mode does not work if only one interface defined
        if rules.proxy and cmp(interfaces[0],interfaces[1])==0:
            raise InitError("Same interface not allowed for output and input in proxy mode!")
       
        #Level tells which parts of a packet should be handled as a payload
        if rules.level[0]!=10 and (rules.level[0]<0 or rules.level[0]>5):
            raise InitError("Level value should be in range [0,4].")
        elif rules.level[0]!=10: decoder.setLevel(rules.level[0])

        #Check whether timer value is positive integer or not
        if rules.timer and rules.timer[0]<=0:
            raise InitError("Erronous timer value.")

        #Go through drop-settings for possible errors
        if rules.drop:
            for item in rules.drop:
                if item not in ['tcp','udp','arp','icmp']:
                    raise InitError("Unsupported/unknown protocol '{}' in drop argument".format(item))

        #Logging specific initialization
        logSettings = {'log':False,'level':'default','format':'default'}
        if rules.log!=None:
            logSettings['log'] = True

            try:
                if rules.log[1] in ['pcap','default']:
                    logSettings['format'] = rules.log[1]
            except IndexError: pass
            
            try:
                logfile = rules.log[0]
                fh = open(logfile)
                if logSettings['format']=='default':
                    print ("Defined logfile already exists. Do you want to "+
                           "add all the new logging information to the end of "+
                           "the defined file?")
                    decision = raw_input("[y/n]: ")
                    while decision!="y" and decision!="n":
                        decision = raw_input("Write 'y' or 'n': ")
                    if decision=="n": logfile = logfile + ".log"
                else:
                    print ("Defined logfile already exists. Pcap-format "+
                           "doesn't support files with multiple sessions. "+
                           "Do you want to overwrite file '"+logfile+"'?")
                    decision = raw_input("[y/n]: ")
                    while decision!="y" and decision!="n":
                        decision = raw_input("Write 'y' or 'n': ")
                    if decision=="n": logfile = logfile + "2.pcap"

            except IOError: pass

            print "Logging to a file named '{0}'".format(logfile)

            if len(rules.log)>2:
                logSettings['level'] = "all"
                print "Logall-mode enabled, logging all the packages send."

            logger = loggerThread(logfile, logSettings, loggingQueue)
            logger.start()

            if rules.log_unaltered:
                logSettings['format'] = "pcap"
                loggerU = loggerThread(logfile+'_unaltered', logSettings, loggingQueue2)
                loggerU.start()
                print "Also logging unaltered stream to a file '{}'".format(logfile+'_unaltered')
        
    except InitError as e:
        print e.msg
        print "Type '-h' or '--help' for more information."
        sys.exit(1)

    pObj = pcap.pcapObject()
    pObj2 = pcap.pcapObject()
    
    maxBytes = 65535
    promiscuous = True
    readTimeout = 100 #In milliseconds

    pObj.open_live(interfaces[0], maxBytes, promiscuous, readTimeout)
    #Prevents pcap from setting a global lock while waiting for packages
    pObj.setnonblock(1)

    if rules.proxy:
        ''' '''
        pObj2.open_live(interfaces[1], maxBytes, promiscuous, readTimeout)
        #Prevents pcap from setting a global lock while waiting for packet
        pObj2.setnonblock(1)
        ''' '''
        #Create separate thread for handling proxy-mode traffic
        pThread = proxyThread([interfaces[1],interfaces[0]])
        pThread.start()

    #Create separate thread for fuzzing and forwarding packets
    fThread = forwarderThread(interfaces[1],fuzzerList,logSettings,filtering,interval=rules.timer)
    fThread.start()
    
    runtime = time.time()

    #Starting the network hoover's main loop
    try:
        print "Network hoover starting.."
        if len(fuzzerList)!=0:
            print "Now fuzzing payload with '{}'".format(fuzzerList[0])
        print "\nProgram execution can be affected with the following keys:"
        print "  's' to swap the input and output interfaces."
        print "  'h' to show some information."
        print "  'q' to quit the program."
        print "  'f' to change fuzzer if more than one defined."
        print "  'i' to turn packet information display on/off."
        print "  't' to enable fuzzer changing timer, if one is defined.\n"

        while 1:
            pObj.dispatch(1, handleInputPacket)

            char = getkey()
            if char:
                if char=='s':
                    print "\nSwapping in process.."
                    #Terminate threads and pcap object
                    fThread.terminate()
                    if rules.proxy:
                        pThread.terminate()
                        del(pObj2)
                    del(pObj)
                    #Swap interfaces
                    interfaces[0],interfaces[1]=interfaces[1],interfaces[0]
                    #Wait until threads have terminated properly
                    Tlock1.acquire()
                    Tlock2.acquire()
                    Tlock1.release()
                    Tlock2.release()
                    #Restart threads and pcap object with new interfaces
                    pObj = pcap.pcapObject()
                    pObj.open_live(interfaces[0],maxBytes,promiscuous,readTimeout)
                    pObj.setnonblock(1)
                    fThread = forwarderThread(interfaces[1],fuzzerList,logSettings,filtering,interval=rules.timer)
                    fThread.start()
                    if rules.proxy:
                        pObj2 = pcap.pcapObject()
                        pObj2.open_live(interfaces[1], maxBytes, promiscuous, readTimeout)
                        pObj2.setnonblock(1)
                        pThread = proxyThread([interfaces[1],interfaces[0]])
                        pThread.start()
                    print "Swapping complete."
                    
                elif char=='i':
                    if pInfo:
                        print " -- Disabling information display --"
                        pInfo = False
                    else:
                        print " -- Enabling information display --"
                        pInfo = True

                elif char=='t':
                    response = fThread.toggleTimer()
                    if response==1:
                        print "-- Fuzzer timer ON --"
                    elif response==0:
                        print "-- Fuzzer timer OFF --"

                elif char=='h':
                    printInfo(interfaces, fThread.getFuzzer(), filtering)

                elif char=='f':
                    fThread.fuzzerChange()

                elif char=='q':
                    break

            if rules.proxy: pObj2.dispatch(1, handleOutputPacket)

    except KeyboardInterrupt, Exception:
        print "Exception happened. Exiting safely.."
    finally:
        print "\nShutting hoover down.."
        print "Hoover was alive {} seconds.".format(repr(time.time()-runtime).split('.')[0])
        print "Packets received by input interface: {0}".format(packetnro)
        if rules.proxy:
            print "Packets received by output interface: {}".format(packetnro2)
        fThread.terminate()
        if rules.proxy:
            pThread.terminate()
        del(pObj)
        if rules.log:
            logger.terminate()
            if rules.log_unaltered:
                loggerU.terminate()
    

class InitError(Exception):
    def __init__(self, msg):
        self.msg = "Error while initializing hoover: {}".format(msg)


'''Not actually an error, just a way to skip things without return'''
class NoError(Exception):
    def __init__(self):
        pass


'''Exception used while constructing packet headers.'''
class BuildError(Exception):
    def __init__(self, proto, msg):
        self.msg = "Exception while building {}-header: {}".format(proto,msg)


'''Prints necessary information about different variables'''
def printInfo(iface, fuzza, filters={}):
    bold = "\033[1m"
    reset = "\033[0;0m"
    print "\nNetwork hoover's currently used settings."
    print "  Listening interface: {}".format(iface[0])
    print "  Output interface: {}".format(iface[1])
    if rules.proxy:
        print "  Proxy mode is ON, also listening interface {}".format(iface[1])
    else:
        print "  Proxy mode is OFF, not listening interface {}".format(iface[1])
    if filters:
        print "  Packet filtering is enabled."
        if 'proto' in filters:
            print "    Protocol(s) processed: {}".format(filters['proto'])
        if 'ipsrc' in filters:
            print "    Source address accepted: {}".format(filters['ipsrc'])
        if 'ipdst' in filters:
            print "    Destination address accepted: {}".format(filters['ipdst'])
        if 'srcport' in filters:
            print "    Source port accepted: {}".format(filters['srcport'])
        if 'dstport' in filters:
            print "    Destination port accepted: {}".format(filters['dstport'])

    if spoofingOpts[0]:
        print "  Address spoofing is enabled."
        for i in range(1, len(spoofingOpts),2):
            print "    '{}' value: {}".format(spoofingOpts[i],spoofingOpts[i+1])
        
    else:
        print "  Packet filtering is not enabled."
    
    if not fuzza:
        print "  Fuzzing is not enabled."
    else:
        print "  Fuzzing is enabled and the following fuzzers are utilized:"
        for x in rules.fuzz:
            print "    {}".format(x)
        print "  Currently used fuzzer: {}".format(fuzza)
        if rules.fuzzing_probability:
            print "  Fuzzing probability: {}".format(rules.fuzzing_probability[0])
        if "Duplicate" in rules.fuzz:
            print "  Duplicate packets are enabled with probability of {}".format(
                rules.duplicate_probability[0])
    if rules.log==None:
        print "  Logging is not enabled."
    else:
        print "  Logging enabled to a file '{}'.".format(rules.log[0])
        if rules.log_unaltered:
            print "  Also logging the unaltered traffic to a file '{}_unaltered'.".format(
                rules.log[0])
    if rules.timer:
        print "  Fuzzer timer is defined, interval is {} seconds.".format(rules.timer[0])
            
    print "\nFunction keys are:"
    print "  's' to swap interfaces."
    print "  'h' to show this information."
    print "  'q' to shut down the hoover and quit."
    print "  'f' to change fuzzer if more than one defined."
    print "  'i' to turn packet information display on/off."
    print "  't' to enable fuzzer changing timer, if one is defined.\n"
        

'''Prints information about available fuzzers.'''
def fuzzerListing():
    bold = "\033[1m"
    reset = "\033[0;0m"
    
    print "\nList of currenty available fuzzers."
    print "To use fuzzers, include arguments '-fuzz <fuzzer1> <fuzzer2> ...'. Names are case sensitive."
    print "If multiple fuzzers are defined, the first one will be used when the hoover starts."
    print "Fuzzers can be changed with 'f' key if there's multiple defined.\n"
    print "Fuzzers can also be changed automatically by defining timer with '--timer' argument and enabling/disabling it with 't' key during the execution."
    print bold+"Enumr"+reset+": The byte enumerator\nEnumr just starts from byte 0, then moves to byte 1 and so on. It doesn't need any input to function.\n"
    print bold+"Flipr"+reset+": The byte flipper\nFlipr randomly selects some places from the samples and flips one randomly chosen bit. The amount of flips depends on the fuzzfactor variable.\n"
    print bold+"Rambo"+reset+": The file mutator\nRambo uses probably the most common fuzzing technique. It selects some random places from a randomly picked sample and removes, repeats, or replaces a byte. The amount of mutations depends on the fuzzfactor variable.\n"
    print bold+"Range"+reset+": The random data generator\nRange generates an test case filled with pseudo-random content.\n"
    print bold+"DistFuzz"+reset+": A fuzzer based on byte distribution\nDistFuzz calculates the byte distribution of all samples and generates test cases so that more probable bytes are more probable in test cases. It also, depending on the fuzzfactor variable, inserts some pseudo-random bytes in some places.\n"
    print bold+"Duplicate"+reset+": Not an actual fuzzer, even though is defined same way as a fuzzer. Sends exact duplicate of a packet with certain probability, which can be changed with '--duplicate-opt' argument. Default probability is 2%."
    print bold+"None"+reset+": This is not a fuzzer. Its only purpose is to disable fuzzing and it will always be available if other fuzzers are defined.\n"


'''Main that is mainly for main purposes'''
if __name__=="__main__":
    main(sys.argv[1:])

